package com.linkoog.devtools.dubbo.parser.xml.index;

import com.intellij.ide.highlighter.XmlFileType;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.indexing.DataIndexer;
import com.intellij.util.indexing.DefaultFileTypeSpecificInputFilter;
import com.intellij.util.indexing.FileBasedIndex;
import com.intellij.util.indexing.FileBasedIndexExtension;
import com.intellij.util.indexing.FileContent;
import com.intellij.util.indexing.ID;
import com.intellij.util.io.DataExternalizer;
import com.intellij.util.io.EnumeratorStringDescriptor;
import com.intellij.util.io.IOUtil;
import com.intellij.util.io.KeyDescriptor;
import com.linkoog.devtools.dubbo.parser.DubboService;
import com.linkoog.devtools.utils.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *  在 dubbo-consumer.xml 或 dubbo_provider.xml 文件中，定义的dubbo服务
 */
public class XmlDubboFileIndex extends FileBasedIndexExtension<String, DubboService> {

    public static final ID<String, DubboService> INDEX_ID = ID.create("DevTools.service.dubbo.index");

    public XmlDubboFileIndex(){
        //FileBasedIndex.getInstance().requestRebuild(INDEX_ID);
    }

    public static Collection<String> getAllServiceInfaces(Project project) {
        return FileBasedIndex.getInstance().getAllKeys(INDEX_ID, project);
    }

    public static DubboService getServiceVoByName(final Project project, String infaceName) {
        if (StringUtils.isBlank(infaceName)){
            return null;
        }

        Collection<DubboService> serviceVos = FileBasedIndex.getInstance().getValues(INDEX_ID, infaceName, createSearchScope(project));
        return serviceVos.stream().findFirst().orElse(null);
        //return FileBasedIndex.getInstance().getContainingFiles(INDEX_ID, infaceName, createSearchScope(project));
    }

    private final DataIndexer<String, DubboService, FileContent> myIndexer = fileContent -> {
        PsiFile file = fileContent.getPsiFile();
        Project project = file.getProject();
        if (!(file instanceof XmlFile)) return Collections.emptyMap();
        XmlFile xmlFile = (XmlFile)file;

        XmlTag rootTag = xmlFile.getRootTag();
        if (rootTag == null) return Collections.emptyMap();

        String nsPrefix = rootTag.getPrefixByNamespace("http://code.alibabatech.com/schema/dubbo");
        if (nsPrefix == null) return Collections.emptyMap();

        List<DubboService> xmlDubboList = new ArrayList<>();
        xmlDubboList.addAll(parseDubboService("xml-provider",  rootTag.findSubTags(getDobboTag(nsPrefix,"service"))));
        xmlDubboList.addAll(parseDubboService("xml-consumer",  rootTag.findSubTags(getDobboTag(nsPrefix,"reference"))));

        Map<String, DubboService> map = new HashMap<>(xmlDubboList.size());
        for (DubboService xmlDubbo : xmlDubboList) {
            map.put(xmlDubbo.getInface(), xmlDubbo);
        }
        return map;
    };

    private List<DubboService> parseDubboService(String sourceType, XmlTag[] serviceTags) {
        List<DubboService> dubboServiceVoList = new ArrayList<>();
        for (XmlTag serviceTag : serviceTags) {
            String inface = serviceTag.getAttributeValue("interface");
            if (StringUtils.isBlank(inface)){
                continue;
            }

            String group = serviceTag.getAttributeValue("group");
            if (group == null) group = "";
            String version = serviceTag.getAttributeValue("version");
            if (version == null || version.startsWith("${")) version = "";

            DubboService dubboService = new DubboService(sourceType, inface, group, version);
            dubboServiceVoList.add(dubboService);
        }
        return dubboServiceVoList;
    }


    private String getDobboTag(String nsPrefix, String tagName){
        if (StringUtils.isBlank(nsPrefix)){
            return tagName;
        }
        return nsPrefix + ":" + tagName;
    }



    @NotNull
    @Override
    public ID<String, DubboService> getName() {
        return INDEX_ID;
    }

    @NotNull
    @Override
    public DataIndexer<String, DubboService, FileContent> getIndexer() {
        return myIndexer;
    }


    @NotNull
    @Override
    public FileBasedIndex.InputFilter getInputFilter() {
        return new DefaultFileTypeSpecificInputFilter(XmlFileType.INSTANCE) {
            @Override
            public boolean acceptInput(@NotNull final VirtualFile file) {
                if ("xsd".equals(file.getExtension())) {
                    return false;
                }
                if (!file.isInLocalFileSystem()) {
                    return false;
                }
                return true;
            }
        };
    }

    protected static GlobalSearchScope createSearchScope(final Project project) {
        final GlobalSearchScope projectScope = GlobalSearchScope.allScope(project);
        return new GlobalSearchScope(project) {
            @Override
            public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
                return projectScope.compare(file1, file2);
            }

            @Override
            public boolean isSearchInModuleContent(@NotNull Module aModule) {
                return true;
            }

            @Override
            public boolean contains(@NotNull VirtualFile file) {
               return true;
            }

            @Override
            public boolean isSearchInLibraries() {
                return true;
            }
        };
    }

    @Override
    @NotNull
    public KeyDescriptor<String> getKeyDescriptor() {
        return EnumeratorStringDescriptor.INSTANCE;
    }

    private static final String NULL_STRING = "\"\"";

    @NotNull
    @Override
    public DataExternalizer<DubboService> getValueExternalizer() {
        return new DataExternalizer<DubboService>() {
            @Override
            public void save(@NotNull DataOutput out, DubboService value) throws IOException {
                IOUtil.writeUTF(out, value.getSourceType() != null ? value.getSourceType() : NULL_STRING);
                IOUtil.writeUTF(out, value.getInface() != null ? value.getInface() : NULL_STRING);
                IOUtil.writeUTF(out, value.getVersion() != null ? value.getVersion() : NULL_STRING);
                IOUtil.writeUTF(out, value.getGroup() != null ? value.getGroup() : NULL_STRING);
            }

            @Override
            public DubboService read(@NotNull DataInput in) throws IOException {
                String sourceType = IOUtil.readUTF(in);
                if (NULL_STRING.equals(sourceType)) sourceType = null;

                String inface = IOUtil.readUTF(in);
                if (NULL_STRING.equals(inface)) inface = null;

                String version = IOUtil.readUTF(in);
                if (NULL_STRING.equals(version)) version = "";

                String group = IOUtil.readUTF(in);
                if (NULL_STRING.equals(group)) group = "";

                return new DubboService(sourceType, inface, version, group);
            }
        };
    }

    @Override
    public boolean dependsOnFileContent() {
        return true;
    }

    @Override
    public int getVersion() {
        return 1;
    }

}
